/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */


package com.sun.jmx.snmp.daemon;

// java import
//

import java.util.Vector;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Level;
import java.io.InterruptedIOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.SocketException;

// jmx imports
//
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.sun.jmx.snmp.SnmpMessage;
import com.sun.jmx.snmp.SnmpPduFactory;
import com.sun.jmx.snmp.SnmpPduBulk;
import com.sun.jmx.snmp.SnmpPduPacket;
import com.sun.jmx.snmp.SnmpPduRequest;
import com.sun.jmx.snmp.SnmpPduTrap;
import com.sun.jmx.snmp.SnmpValue;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpVarBindList;
import com.sun.jmx.snmp.SnmpDefinitions;
import com.sun.jmx.snmp.SnmpStatusException;
import com.sun.jmx.snmp.SnmpTooBigException;
import com.sun.jmx.snmp.SnmpDataTypeEnums;

// RI imports
//
import static com.sun.jmx.defaults.JmxProperties.SNMP_ADAPTOR_LOGGER;

// SNMP runtime import
//
import com.sun.jmx.snmp.agent.SnmpMibAgent;
import com.sun.jmx.snmp.agent.SnmpUserDataFactory;
//import com.sun.jmx.snmp.IPAcl.IPAcl;
import com.sun.jmx.snmp.InetAddressAcl;


class SnmpRequestHandler extends ClientHandler implements SnmpDefinitions {

  private transient DatagramSocket socket = null;
  private transient DatagramPacket packet = null;
  private transient Vector<SnmpMibAgent> mibs = null;

  /**
   * Contains the list of sub-requests associated to the current request.
   */
  private transient Hashtable<SnmpMibAgent, SnmpSubRequestHandler> subs = null;

  /**
   * Reference on the MIBS
   */
  private transient SnmpMibTree root;

  private transient InetAddressAcl ipacl = null;
  private transient SnmpPduFactory pduFactory = null;
  private transient SnmpUserDataFactory userDataFactory = null;
  private transient SnmpAdaptorServer adaptor = null;

  /**
   * Full constructor
   */
  public SnmpRequestHandler(SnmpAdaptorServer server, int id,
      DatagramSocket s, DatagramPacket p,
      SnmpMibTree tree, Vector<SnmpMibAgent> m,
      InetAddressAcl a,
      SnmpPduFactory factory,
      SnmpUserDataFactory dataFactory,
      MBeanServer f, ObjectName n) {
    super(server, id, f, n);

    // Need a reference on SnmpAdaptorServer for getNext & getBulk,
    // in case of oid equality (mib overlapping).
    //
    adaptor = server;
    socket = s;
    packet = p;
    root = tree;
    mibs = new Vector<>(m);
    subs = new Hashtable<>(mibs.size());
    ipacl = a;
    pduFactory = factory;
    userDataFactory = dataFactory;
    //thread.start();
  }

  /**
   * Treat the request available in 'packet' and send the result
   * back to the client.
   * Note: we overwrite 'packet' with the response bytes.
   */
  @Override
  public void doRun() {

    // Trace the input packet
    //
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "doRun", "Packet received:\n" +
              SnmpMessage.dumpHexBuffer(packet.getData(), 0, packet.getLength()));
    }

    // Let's build the response packet
    //
    DatagramPacket respPacket = makeResponsePacket(packet);

    // Trace the output packet
    //
    if ((SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) && (respPacket != null)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "doRun", "Packet to be sent:\n" +
              SnmpMessage.dumpHexBuffer(respPacket.getData(), 0, respPacket.getLength()));
    }

    // Send the response packet if any
    //
    if (respPacket != null) {
      try {
        socket.send(respPacket);
      } catch (SocketException e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          if (e.getMessage().equals(InterruptSysCallMsg)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "doRun", "interrupted");
          } else {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "doRun", "I/O exception", e);
          }
        }
      } catch (InterruptedIOException e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "doRun", "interrupted");
        }
      } catch (Exception e) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "doRun", "failure when sending response", e);
        }
      }
    }
  }

  /**
   * Here we make a response packet from a request packet.
   * We return null if there no response packet to sent.
   */
  private DatagramPacket makeResponsePacket(DatagramPacket reqPacket) {
    DatagramPacket respPacket = null;

    // Transform the request packet into a request SnmpMessage
    //
    SnmpMessage reqMsg = new SnmpMessage();
    try {
      reqMsg.decodeMessage(reqPacket.getData(), reqPacket.getLength());
      reqMsg.address = reqPacket.getAddress();
      reqMsg.port = reqPacket.getPort();
    } catch (SnmpStatusException x) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "makeResponsePacket", "packet decoding failed", x);
      }
      reqMsg = null;
      ((SnmpAdaptorServer) adaptorServer).incSnmpInASNParseErrs(1);
    }

    // Make the response SnmpMessage if any
    //
    SnmpMessage respMsg = null;
    if (reqMsg != null) {
      respMsg = makeResponseMessage(reqMsg);
    }

    // Try to transform the response SnmpMessage into response packet.
    // NOTE: we overwrite the request packet.
    //
    if (respMsg != null) {
      try {
        reqPacket.setLength(respMsg.encodeMessage(reqPacket.getData()));
        respPacket = reqPacket;
      } catch (SnmpTooBigException x) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "makeResponsePacket", "response message is too big");
        }
        try {
          respMsg = newTooBigMessage(reqMsg);
          reqPacket.setLength(respMsg.encodeMessage(reqPacket.getData()));
          respPacket = reqPacket;
        } catch (SnmpTooBigException xx) {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "makeResponsePacket", "'too big' is 'too big' !!!");
          }
          adaptor.incSnmpSilentDrops(1);
        }
      }
    }

    return respPacket;
  }

  /**
   * Here we make a response message from a request message.
   * We return null if there is no message to reply.
   */
  private SnmpMessage makeResponseMessage(SnmpMessage reqMsg) {
    SnmpMessage respMsg = null;

    // Transform the request message into a request pdu
    //
    SnmpPduPacket reqPdu;
    Object userData = null;
    try {
      reqPdu = (SnmpPduPacket) pduFactory.decodeSnmpPdu(reqMsg);
      if (reqPdu != null && userDataFactory != null) {
        userData = userDataFactory.allocateUserData(reqPdu);
      }
    } catch (SnmpStatusException x) {
      reqPdu = null;
      SnmpAdaptorServer snmpServer = (SnmpAdaptorServer) adaptorServer;
      snmpServer.incSnmpInASNParseErrs(1);
      if (x.getStatus() == SnmpDefinitions.snmpWrongSnmpVersion) {
        snmpServer.incSnmpInBadVersions(1);
      }
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "makeResponseMessage", "message decoding failed", x);
      }
    }

    // Make the response pdu if any
    //
    SnmpPduPacket respPdu = null;
    if (reqPdu != null) {
      respPdu = makeResponsePdu(reqPdu, userData);
      try {
        if (userDataFactory != null) {
          userDataFactory.releaseUserData(userData, respPdu);
        }
      } catch (SnmpStatusException x) {
        respPdu = null;
      }
    }

    // Try to transform the response pdu into a response message if any
    //
    if (respPdu != null) {
      try {
        respMsg = (SnmpMessage) pduFactory.
            encodeSnmpPdu(respPdu, packet.getData().length);
      } catch (SnmpStatusException x) {
        respMsg = null;
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "makeResponseMessage", "failure when encoding the response message", x);
        }
      } catch (SnmpTooBigException x) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "makeResponseMessage", "response message is too big");
        }

        try {
          // if the PDU is too small, why should we try to do
          // recovery ?
          //
          if (packet.getData().length <= 32) {
            throw x;
          }
          int pos = x.getVarBindCount();
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "makeResponseMessage", "fail on element" + pos);
          }
          int old;
          while (true) {
            try {
              respPdu = reduceResponsePdu(reqPdu, respPdu, pos);
              respMsg = (SnmpMessage) pduFactory.
                  encodeSnmpPdu(respPdu,
                      packet.getData().length - 32);
              break;
            } catch (SnmpTooBigException xx) {
              if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
                SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                    "makeResponseMessage", "response message is still too big");
              }
              old = pos;
              pos = xx.getVarBindCount();
              if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
                SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                    "makeResponseMessage", "fail on element" + pos);
              }
              if (pos == old) {
                // we can not go any further in trying to
                // reduce the message !
                //
                throw xx;
              }
            }
          }// end of loop
        } catch (SnmpStatusException xx) {
          respMsg = null;
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "makeResponseMessage", "failure when encoding the response message", xx);
          }
        } catch (SnmpTooBigException xx) {
          try {
            respPdu = newTooBigPdu(reqPdu);
            respMsg = (SnmpMessage) pduFactory.
                encodeSnmpPdu(respPdu, packet.getData().length);
          } catch (SnmpTooBigException xxx) {
            respMsg = null;
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
              SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                  "makeResponseMessage", "'too big' is 'too big' !!!");
            }
            adaptor.incSnmpSilentDrops(1);
          } catch (Exception xxx) {
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
              SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                  "makeResponseMessage", "Got unexpected exception", xxx);
            }
            respMsg = null;
          }
        } catch (Exception xx) {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "makeResponseMessage", "Got unexpected exception", xx);
          }
          respMsg = null;
        }
      }
    }
    return respMsg;
  }

  /**
   * Here we make a response pdu from a request pdu.
   * We return null if there is no pdu to reply.
   */
  private SnmpPduPacket makeResponsePdu(SnmpPduPacket reqPdu,
      Object userData) {

    SnmpAdaptorServer snmpServer = (SnmpAdaptorServer) adaptorServer;
    SnmpPduPacket respPdu = null;

    snmpServer.updateRequestCounters(reqPdu.type);
    if (reqPdu.varBindList != null) {
      snmpServer.updateVarCounters(reqPdu.type,
          reqPdu.varBindList.length);
    }

    if (checkPduType(reqPdu)) {
      respPdu = checkAcl(reqPdu);
      if (respPdu == null) { // reqPdu is accepted by ACLs
        if (mibs.size() < 1) {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "makeResponsePdu", "Request " + reqPdu.requestId +
                    " received but no MIB registered.");
          }
          return makeNoMibErrorPdu((SnmpPduRequest) reqPdu, userData);
        }
        switch (reqPdu.type) {
          case SnmpPduPacket.pduGetRequestPdu:
          case SnmpPduPacket.pduGetNextRequestPdu:
          case SnmpPduPacket.pduSetRequestPdu:
            respPdu = makeGetSetResponsePdu((SnmpPduRequest) reqPdu,
                userData);
            break;

          case SnmpPduPacket.pduGetBulkRequestPdu:
            respPdu = makeGetBulkResponsePdu((SnmpPduBulk) reqPdu,
                userData);
            break;
        }
      } else { // reqPdu is rejected by ACLs
        // respPdu contains the error response to be sent.
        // We send this response only if authResEnabled is true.
        if (!snmpServer.getAuthRespEnabled()) { // No response should be sent
          respPdu = null;
        }
        if (snmpServer.getAuthTrapEnabled()) { // A trap must be sent
          try {
            snmpServer.snmpV1Trap(SnmpPduTrap.
                    trapAuthenticationFailure, 0,
                new SnmpVarBindList());
          } catch (Exception x) {
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
              SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                  "makeResponsePdu", "Failure when sending authentication trap", x);
            }
          }
        }
      }
    }
    return respPdu;
  }

  //
  // Generates a response packet, filling the values in the
  // varbindlist with one of endOfMibView, noSuchObject, noSuchInstance
  // according to the value of <code>status</code>
  //
  // @param statusTag should be one of:
  //        <li>SnmpDataTypeEnums.errEndOfMibViewTag</li>
  //        <li>SnmpDataTypeEnums.errNoSuchObjectTag</li>
  //        <li>SnmpDataTypeEnums.errNoSuchInstanceTag</li>
  //
  SnmpPduPacket makeErrorVarbindPdu(SnmpPduPacket req, int statusTag) {

    final SnmpVarBind[] vblist = req.varBindList;
    final int length = vblist.length;

    switch (statusTag) {
      case SnmpDataTypeEnums.errEndOfMibViewTag:
        for (int i = 0; i < length; i++) {
          vblist[i].value = SnmpVarBind.endOfMibView;
        }
        break;
      case SnmpDataTypeEnums.errNoSuchObjectTag:
        for (int i = 0; i < length; i++) {
          vblist[i].value = SnmpVarBind.noSuchObject;
        }
        break;
      case SnmpDataTypeEnums.errNoSuchInstanceTag:
        for (int i = 0; i < length; i++) {
          vblist[i].value = SnmpVarBind.noSuchInstance;
        }
        break;
      default:
        return newErrorResponsePdu(req, snmpRspGenErr, 1);
    }
    return newValidResponsePdu(req, vblist);
  }

  // Generates an appropriate response when no mib is registered in
  // the adaptor.
  //
  // <li>If the version is V1:</li>
  // <ul><li>Generates a NoSuchName error V1 response PDU</li></ul>
  // <li>If the version is V2:</li>
  // <ul><li>If the request is a GET, fills the varbind list with
  //         NoSuchObject's</li>
  //     <li>If the request is a GET-NEXT/GET-BULK, fills the varbind
  //         list with EndOfMibView's</li>
  //     <li>If the request is a SET, generates a NoAccess error V2
  //          response PDU</li>
  // </ul>
  //
  //
  SnmpPduPacket makeNoMibErrorPdu(SnmpPduRequest req, Object userData) {
    // There is no agent registered
    //
    if (req.version == SnmpDefinitions.snmpVersionOne) {
      // Version 1: => NoSuchName
      return
          newErrorResponsePdu(req, snmpRspNoSuchName, 1);
    } else if (req.version == SnmpDefinitions.snmpVersionTwo) {
      // Version 2: => depends on PDU type
      switch (req.type) {
        case pduSetRequestPdu:
        case pduWalkRequest:
          // SET request => NoAccess
          return
              newErrorResponsePdu(req, snmpRspNoAccess, 1);
        case pduGetRequestPdu:
          // GET request => NoSuchObject
          return
              makeErrorVarbindPdu(req, SnmpDataTypeEnums.
                  errNoSuchObjectTag);
        case pduGetNextRequestPdu:
        case pduGetBulkRequestPdu:
          // GET-NEXT or GET-BULK => EndOfMibView
          return
              makeErrorVarbindPdu(req, SnmpDataTypeEnums.
                  errEndOfMibViewTag);
        default:
      }
    }
    // Something wrong here: => snmpRspGenErr
    return newErrorResponsePdu(req, snmpRspGenErr, 1);
  }

  /**
   * Here we make the response pdu from a get/set request pdu.
   * At this level, the result is never null.
   */
  private SnmpPduPacket makeGetSetResponsePdu(SnmpPduRequest req,
      Object userData) {

    // Create the trhead group specific for handling sub-requests
    // associated to the current request. Use the invoke id
    //
    // Nice idea to use a thread group on a request basis.
    // However the impact on performance is terrible !
    // theGroup= new ThreadGroup(thread.getThreadGroup(),
    //                "request " + String.valueOf(req.requestId));

    // Let's build the varBindList for the response pdu
    //

    if (req.varBindList == null) {
      // Good ! Let's make a full response pdu.
      //
      return newValidResponsePdu(req, null);
    }

    // First we need to split the request into subrequests
    //
    splitRequest(req);
    int nbSubRequest = subs.size();
    if (nbSubRequest == 1) {
      return turboProcessingGetSet(req, userData);
    }

    // Execute all the subrequests resulting from the split of the
    // varbind list.
    //
    SnmpPduPacket result = executeSubRequest(req, userData);
    if (result != null)
    // It means that an error occurred. The error is already
    // formatted by the executeSubRequest
    // method.
    {
      return result;
    }

    // So far so good. So we need to concatenate all the answers.
    //
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "makeGetSetResponsePdu",
          "Build the unified response for request " + req.requestId);
    }
    return mergeResponses(req);
  }

  /**
   * The method runs all the sub-requests associated to the current
   * instance of SnmpRequestHandler.
   */
  private SnmpPduPacket executeSubRequest(SnmpPduPacket req,
      Object userData) {

    int errorStatus = SnmpDefinitions.snmpRspNoError;

    int i;
    // If it's a set request, we must first check any varBind
    //
    if (req.type == pduSetRequestPdu) {

      i = 0;
      for (Enumeration<SnmpSubRequestHandler> e = subs.elements(); e.hasMoreElements(); i++) {
        // Indicate to the sub request that a check must be invoked ...
        // OK we should have defined out own tag for that !
        //
        SnmpSubRequestHandler sub = e.nextElement();
        sub.setUserData(userData);
        sub.type = pduWalkRequest;

        sub.run();

        sub.type = pduSetRequestPdu;

        if (sub.getErrorStatus() != SnmpDefinitions.snmpRspNoError) {
          // No point to go any further.
          //
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
                "executeSubRequest", "an error occurs");
          }

          return newErrorResponsePdu(req, errorStatus,
              sub.getErrorIndex() + 1);
        }
      }
    }// end processing check operation for a set PDU.

    // Let's start the sub-requests.
    //
    i = 0;
    for (Enumeration<SnmpSubRequestHandler> e = subs.elements(); e.hasMoreElements(); i++) {
      SnmpSubRequestHandler sub = e.nextElement();
        /* NPCTE fix for bugId 4492741, esc 0, 16-August 2001 */
      sub.setUserData(userData);
        /* end of NPCTE fix for bugId 4492741 */

      sub.run();

      if (sub.getErrorStatus() != SnmpDefinitions.snmpRspNoError) {
        // No point to go any further.
        //
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "executeSubRequest", "an error occurs");
        }

        return newErrorResponsePdu(req, errorStatus,
            sub.getErrorIndex() + 1);
      }
    }

    // everything is ok
    //
    return null;
  }

  /**
   * Optimize when there is only one sub request
   */
  private SnmpPduPacket turboProcessingGetSet(SnmpPduRequest req,
      Object userData) {

    int errorStatus;
    SnmpSubRequestHandler sub = subs.elements().nextElement();
    sub.setUserData(userData);

    // Indicate to the sub request that a check must be invoked ...
    // OK we should have defined out own tag for that !
    //
    if (req.type == SnmpDefinitions.pduSetRequestPdu) {
      sub.type = pduWalkRequest;
      sub.run();
      sub.type = pduSetRequestPdu;

      // Check the error status.
      //
      errorStatus = sub.getErrorStatus();
      if (errorStatus != SnmpDefinitions.snmpRspNoError) {
        // No point to go any further.
        //
        return newErrorResponsePdu(req, errorStatus,
            sub.getErrorIndex() + 1);
      }
    }

    // process the operation
    //

    sub.run();
    errorStatus = sub.getErrorStatus();
    if (errorStatus != SnmpDefinitions.snmpRspNoError) {
      // No point to go any further.
      //
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "turboProcessingGetSet", "an error occurs");
      }
      int realIndex = sub.getErrorIndex() + 1;
      return newErrorResponsePdu(req, errorStatus, realIndex);
    }

    // So far so good. So we need to concatenate all the answers.
    //

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
          "turboProcessingGetSet", "build the unified response for request "
              + req.requestId);
    }
    return mergeResponses(req);
  }

  /**
   * Here we make the response pdu for a bulk request.
   * At this level, the result is never null.
   */
  private SnmpPduPacket makeGetBulkResponsePdu(SnmpPduBulk req,
      Object userData) {

    SnmpVarBind[] respVarBindList;

    // RFC 1905, Section 4.2.3, p14
    int L = req.varBindList.length;
    int N = Math.max(Math.min(req.nonRepeaters, L), 0);
    int M = Math.max(req.maxRepetitions, 0);
    int R = L - N;

    if (req.varBindList == null) {
      // Good ! Let's make a full response pdu.
      //
      return newValidResponsePdu(req, null);
    }

    // Split the request into subrequests.
    //
    splitBulkRequest(req, N, M, R);
    SnmpPduPacket result = executeSubRequest(req, userData);
    if (result != null) {
      return result;
    }

    respVarBindList = mergeBulkResponses(N + (M * R));

    // Now we remove useless trailing endOfMibView.
    //
    int m2; // respVarBindList[m2] item and next are going to be removed
    int t = respVarBindList.length;
    while ((t > N) && (respVarBindList[t - 1].
        value.equals(SnmpVarBind.endOfMibView))) {
      t--;
    }
    if (t == N) {
      m2 = N + R;
    } else {
      m2 = N + ((t - 1 - N) / R + 2) * R; // Trivial, of course...
    }
    if (m2 < respVarBindList.length) {
      SnmpVarBind[] truncatedList = new SnmpVarBind[m2];
      for (int i = 0; i < m2; i++) {
        truncatedList[i] = respVarBindList[i];
      }
      respVarBindList = truncatedList;
    }

    // Good ! Let's make a full response pdu.
    //
    return newValidResponsePdu(req, respVarBindList);
  }

  /**
   * Check the type of the pdu: only the get/set/bulk request
   * are accepted.
   */
  private boolean checkPduType(SnmpPduPacket pdu) {

    boolean result;

    switch (pdu.type) {

      case SnmpDefinitions.pduGetRequestPdu:
      case SnmpDefinitions.pduGetNextRequestPdu:
      case SnmpDefinitions.pduSetRequestPdu:
      case SnmpDefinitions.pduGetBulkRequestPdu:
        result = true;
        break;

      default:
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
              "checkPduType", "cannot respond to this kind of PDU");
        }
        result = false;
        break;
    }

    return result;
  }

  /**
   * Check if the specified pdu is conform to the ACL.
   * This method returns null if the pdu is ok. If not, it returns
   * the response pdu to be replied.
   */
  private SnmpPduPacket checkAcl(SnmpPduPacket pdu) {
    SnmpPduPacket response = null;
    String community = new String(pdu.community);

    // We check the pdu type and create an error response if
    // the check failed.
    //
    if (ipacl != null) {
      if (pdu.type == SnmpDefinitions.pduSetRequestPdu) {
        if (!ipacl.checkWritePermission(pdu.address, community)) {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "checkAcl", "sender is " + pdu.address +
                    " with " + community + ". Sender has no write permission");
          }
          int err = SnmpSubRequestHandler.
              mapErrorStatus(SnmpDefinitions.
                      snmpRspAuthorizationError,
                  pdu.version, pdu.type);
          response = newErrorResponsePdu(pdu, err, 0);
        } else {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "checkAcl", "sender is " + pdu.address +
                    " with " + community + ". Sender has write permission");
          }
        }
      } else {
        if (!ipacl.checkReadPermission(pdu.address, community)) {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "checkAcl", "sender is " + pdu.address +
                    " with " + community + ". Sender has no read permission");
          }
          int err = SnmpSubRequestHandler.
              mapErrorStatus(SnmpDefinitions.
                      snmpRspAuthorizationError,
                  pdu.version, pdu.type);
          response = newErrorResponsePdu(pdu,
              err,
              0);
          SnmpAdaptorServer snmpServer =
              (SnmpAdaptorServer) adaptorServer;
          snmpServer.updateErrorCounters(SnmpDefinitions.
              snmpRspNoSuchName);
        } else {
          if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
            SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
                "checkAcl", "sender is " + pdu.address +
                    " with " + community + ". Sender has read permission");
          }
        }
      }
    }

    // If the response is not null, this means the pdu is rejected.
    // So let's update the statistics.
    //
    if (response != null) {
      SnmpAdaptorServer snmpServer = (SnmpAdaptorServer) adaptorServer;
      snmpServer.incSnmpInBadCommunityUses(1);
      if (ipacl.checkCommunity(community) == false) {
        snmpServer.incSnmpInBadCommunityNames(1);
      }
    }

    return response;
  }

  /**
   * Make a response pdu with the specified error status and index.
   * NOTE: the response pdu share its varBindList with the request pdu.
   */
  private SnmpPduRequest newValidResponsePdu(SnmpPduPacket reqPdu,
      SnmpVarBind[] varBindList) {
    SnmpPduRequest result = new SnmpPduRequest();

    result.address = reqPdu.address;
    result.port = reqPdu.port;
    result.version = reqPdu.version;
    result.community = reqPdu.community;
    result.type = SnmpPduRequest.pduGetResponsePdu;
    result.requestId = reqPdu.requestId;
    result.errorStatus = SnmpDefinitions.snmpRspNoError;
    result.errorIndex = 0;
    result.varBindList = varBindList;

    ((SnmpAdaptorServer) adaptorServer).
        updateErrorCounters(result.errorStatus);

    return result;
  }

  /**
   * Make a response pdu with the specified error status and index.
   * NOTE: the response pdu share its varBindList with the request pdu.
   */
  private SnmpPduRequest newErrorResponsePdu(SnmpPduPacket req, int s, int i) {
    SnmpPduRequest result = newValidResponsePdu(req, null);
    result.errorStatus = s;
    result.errorIndex = i;
    result.varBindList = req.varBindList;

    ((SnmpAdaptorServer) adaptorServer).
        updateErrorCounters(result.errorStatus);

    return result;
  }

  private SnmpMessage newTooBigMessage(SnmpMessage reqMsg)
      throws SnmpTooBigException {
    SnmpMessage result = null;
    SnmpPduPacket reqPdu;

    try {
      reqPdu = (SnmpPduPacket) pduFactory.decodeSnmpPdu(reqMsg);
      if (reqPdu != null) {
        SnmpPduPacket respPdu = newTooBigPdu(reqPdu);
        result = (SnmpMessage) pduFactory.
            encodeSnmpPdu(respPdu, packet.getData().length);
      }
    } catch (SnmpStatusException x) {
      // This should not occur because decodeIncomingRequest has normally
      // been successfully called before.
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "newTooBigMessage", "Internal error", x);
      }
      throw new InternalError(x);
    }

    return result;
  }

  private SnmpPduPacket newTooBigPdu(SnmpPduPacket req) {
    SnmpPduRequest result =
        newErrorResponsePdu(req, SnmpDefinitions.snmpRspTooBig, 0);
    result.varBindList = null;
    return result;
  }

  private SnmpPduPacket reduceResponsePdu(SnmpPduPacket req,
      SnmpPduPacket resp,
      int acceptedVbCount)
      throws SnmpTooBigException {

    // Reduction can be attempted only on bulk response
    //
    if (req.type != SnmpPduPacket.pduGetBulkRequestPdu) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "reduceResponsePdu", "cannot remove anything");
      }
      throw new SnmpTooBigException(acceptedVbCount);
    }

    // We're going to reduce the varbind list.
    // First determine which items should be removed.
    // Next duplicate and replace the existing list by the reduced one.
    //
    // acceptedVbCount is the number of varbind which have been
    // successfully encoded before reaching bufferSize:
    //   * when it is >= 2, we split the varbindlist at this
    //     position (-1 to be safe),
    //   * when it is 1, we only put one (big?) item in the varbindlist
    //   * when it is 0 (in fact, acceptedVbCount is not available),
    //     we split the varbindlist by 2.
    //
    int vbCount;
    if (acceptedVbCount >= 3) {
      vbCount = Math.min(acceptedVbCount - 1, resp.varBindList.length);
    } else if (acceptedVbCount == 1) {
      vbCount = 1;
    } else // acceptedCount == 0 ie it is unknown
    {
      vbCount = resp.varBindList.length / 2;
    }

    if (vbCount < 1) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "reduceResponsePdu", "cannot remove anything");
      }
      throw new SnmpTooBigException(acceptedVbCount);
    } else {
      SnmpVarBind[] newVbList = new SnmpVarBind[vbCount];
      for (int i = 0; i < vbCount; i++) {
        newVbList[i] = resp.varBindList[i];
      }
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINEST, dbgTag,
            "reduceResponsePdu", (resp.varBindList.length - newVbList.length) +
                " items have been removed");
      }
      resp.varBindList = newVbList;
    }

    return resp;
  }

  /**
   * The method takes the incoming requests and split it into subrequests.
   */
  private void splitRequest(SnmpPduRequest req) {

    int nbAgents = mibs.size();
    SnmpMibAgent agent = mibs.firstElement();
    if (nbAgents == 1) {
      // Take all the oids contained in the request and
      //
      subs.put(agent, new SnmpSubRequestHandler(agent, req, true));
      return;
    }

    // For the get next operation we are going to send the varbind list
    // to all agents
    //
    if (req.type == pduGetNextRequestPdu) {
      for (Enumeration<SnmpMibAgent> e = mibs.elements(); e.hasMoreElements(); ) {
        final SnmpMibAgent ag = e.nextElement();
        subs.put(ag, new SnmpSubNextRequestHandler(adaptor, ag, req));
      }
      return;
    }

    int nbReqs = req.varBindList.length;
    SnmpVarBind[] vars = req.varBindList;
    SnmpSubRequestHandler sub;
    for (int i = 0; i < nbReqs; i++) {
      agent = root.getAgentMib(vars[i].oid);
      sub = subs.get(agent);
      if (sub == null) {
        // We need to create the sub request handler and update
        // the hashtable
        //
        sub = new SnmpSubRequestHandler(agent, req);
        subs.put(agent, sub);
      }

      // Update the translation table within the subrequest
      //
      sub.updateRequest(vars[i], i);
    }
  }

  /**
   * The method takes the incoming get bulk requests and split it into
   * subrequests.
   */
  private void splitBulkRequest(SnmpPduBulk req,
      int nonRepeaters,
      int maxRepetitions,
      int R) {
    // Send the getBulk to all agents
    //
    for (Enumeration<SnmpMibAgent> e = mibs.elements(); e.hasMoreElements(); ) {
      final SnmpMibAgent agent = e.nextElement();

      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(Level.FINER, dbgTag,
            "splitBulkRequest", "Create a sub with : " + agent + " " + nonRepeaters
                + " " + maxRepetitions + " " + R);
      }

      subs.put(agent,
          new SnmpSubBulkRequestHandler(adaptor,
              agent,
              req,
              nonRepeaters,
              maxRepetitions,
              R));
    }
  }

  private SnmpPduPacket mergeResponses(SnmpPduRequest req) {

    if (req.type == pduGetNextRequestPdu) {
      return mergeNextResponses(req);
    }

    SnmpVarBind[] result = req.varBindList;

    // Go through the list of subrequests and concatenate.
    // Hopefully, by now all the sub-requests should be finished
    //
    for (Enumeration<SnmpSubRequestHandler> e = subs.elements(); e.hasMoreElements(); ) {
      SnmpSubRequestHandler sub = e.nextElement();
      sub.updateResult(result);
    }
    return newValidResponsePdu(req, result);
  }

  private SnmpPduPacket mergeNextResponses(SnmpPduRequest req) {
    int max = req.varBindList.length;
    SnmpVarBind[] result = new SnmpVarBind[max];

    // Go through the list of subrequests and concatenate.
    // Hopefully, by now all the sub-requests should be finished
    //
    for (Enumeration<SnmpSubRequestHandler> e = subs.elements(); e.hasMoreElements(); ) {
      SnmpSubRequestHandler sub = e.nextElement();
      sub.updateResult(result);
    }

    if (req.version == snmpVersionTwo) {
      return newValidResponsePdu(req, result);
    }

    // In v1 make sure there is no endOfMibView ...
    //
    for (int i = 0; i < max; i++) {
      SnmpValue val = result[i].value;
      if (val == SnmpVarBind.endOfMibView) {
        return newErrorResponsePdu(req,
            SnmpDefinitions.snmpRspNoSuchName, i + 1);
      }
    }

    // So far so good ...
    //
    return newValidResponsePdu(req, result);
  }

  private SnmpVarBind[] mergeBulkResponses(int size) {
    // Let's allocate the array for storing the result
    //
    SnmpVarBind[] result = new SnmpVarBind[size];
    for (int i = size - 1; i >= 0; --i) {
      result[i] = new SnmpVarBind();
      result[i].value = SnmpVarBind.endOfMibView;
    }

    // Go through the list of subrequests and concatenate.
    // Hopefully, by now all the sub-requests should be finished
    //
    for (Enumeration<SnmpSubRequestHandler> e = subs.elements(); e.hasMoreElements(); ) {
      SnmpSubRequestHandler sub = e.nextElement();
      sub.updateResult(result);
    }

    return result;
  }

  @Override
  protected String makeDebugTag() {
    return "SnmpRequestHandler[" + adaptorServer.getProtocol() + ":" +
        adaptorServer.getPort() + "]";
  }

  @Override
  Thread createThread(Runnable r) {
    return null;
  }

  static final private String InterruptSysCallMsg =
      "Interrupted system call";
}
